package com.github.grpc.client.configuration;

import com.github.grpc.client.property.GrpcChannelsProperties;
import com.github.grpc.client.service.GrpcChannelFactory;
import com.github.grpc.client.service.GrpcClientBeanPostProcessor;
import com.github.grpc.client.service.impl.AddressChannelFactory;
import com.github.grpc.client.service.impl.EtcdClientChannelFactory;
import com.github.grpc.client.annotation.GrpcClient;
import com.github.grpc.client.interceptor.TraceClientInterceptor;
import com.github.grpc.client.service.GlobalClientInterceptorRegistry;
import com.github.grpc.client.service.MetadataInjector;
import io.grpc.LoadBalancer;
import io.grpc.util.RoundRobinLoadBalancerFactory;
import mousio.etcd4j.EtcdClient;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Grpc客户端配置
 */
@Configuration
@AutoConfigureAfter(EtcdAutoConfiguration.class)
@EnableConfigurationProperties
@ConditionalOnClass({GrpcChannelFactory.class})
public class GrpcClientAutoConfiguration {

    @ConditionalOnMissingBean
    @Bean
    public GrpcChannelsProperties grpcChannelsProperties() {
        return new GrpcChannelsProperties();
    }

    @Bean
    public GlobalClientInterceptorRegistry globalClientInterceptorRegistry() {
        return new GlobalClientInterceptorRegistry();
    }

    @ConditionalOnMissingBean
    @Bean
    public LoadBalancer.Factory grpcLoadBalancerFactory() {
        return RoundRobinLoadBalancerFactory.getInstance();
    }

    @ConditionalOnProperty(value = "grpc.register", havingValue = "false")
    @Bean
    public GrpcChannelFactory addressChannelFactory(GrpcChannelsProperties channels, LoadBalancer.Factory loadBalancerFactory, GlobalClientInterceptorRegistry globalClientInterceptorRegistry) {
        return new AddressChannelFactory(channels, loadBalancerFactory, globalClientInterceptorRegistry);
    }

    @ConditionalOnProperty(value = "grpc.register", havingValue = "true")
    @Bean
    public GrpcChannelFactory etcdChannelFactory(GrpcChannelsProperties channels, EtcdClient etcdClient, LoadBalancer.Factory loadBalancerFactory, GlobalClientInterceptorRegistry globalClientInterceptorRegistry) {
        return new EtcdClientChannelFactory(channels,etcdClient,loadBalancerFactory, globalClientInterceptorRegistry);
    }

    @Bean
    @ConditionalOnClass(GrpcClient.class)
    public GrpcClientBeanPostProcessor grpcClientBeanPostProcessor() {
        return new GrpcClientBeanPostProcessor();
    }


    @Configuration
    @ConditionalOnProperty(value = "spring.sleuth.scheduled.enabled", matchIfMissing = true)
    @ConditionalOnClass(Tracer.class)
    protected static class TraceClientAutoConfiguration {

        @Bean
        public BeanPostProcessor clientInterceptorPostProcessor(GlobalClientInterceptorRegistry registry) {
            return new ClientInterceptorPostProcessor(registry);
        }

        private static class ClientInterceptorPostProcessor implements BeanPostProcessor {

            private GlobalClientInterceptorRegistry registry;

            public ClientInterceptorPostProcessor(
                    GlobalClientInterceptorRegistry registry) {
                this.registry = registry;
            }

            @Override
            public Object postProcessBeforeInitialization(Object bean,
                                                          String beanName)
                    throws BeansException {
                return bean;
            }

            @Override
            public Object postProcessAfterInitialization(Object bean,
                                                         String beanName)
                    throws BeansException {
                if (bean instanceof Tracer) {
                    this.registry.addClientInterceptors(new TraceClientInterceptor((Tracer) bean, new MetadataInjector()));
                }
                return bean;
            }
        }
    }

}

